########################################################################
#  searxpy - Provides Python modules for interacting with searx and
#       searx-stats2 instances.
#  Copyright (C) 2020  CYBERDEViL
#
#  This file is part of searxpy.
#
#  searxpy is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  searxpy is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
########################################################################

import requests
import json


class SearchEngine:
    def __init__(self, name, data):
        """Model for a search engine data.

        @param name: Name of the search engine
        @type name: str

        @param data: Data of the search engine
        @type data: dict
        """
        self._name = name
        self._data = data

    def __repr__(self): return self._name

    @property
    def name(self):
        """
        @return: returns the name of this search engine.
        @rtype: str
        """
        return self._name

    @property
    def enabled(self):
        """If this instance has this search engine enabled or not.

        @return:
        @rtype: bool
        """
        return self._data.get('enabled', False)

    @property
    def stats(self):
        """

        @return:
        @rtype: bool
        """
        return self._data.get('stats', False)


class TLS:
    def __init__(self, data):
        """Model for a instance it's TLS data.

        @param data: dict with the instance it's TLS data.
        @type data: dict
        """
        self._data = data

    @property
    def data(self): return self._data

    @property
    def version(self):
        """Returns the TLS version used.

        @return:
        @rtype: str
        """
        return self.data.get('version', "")

    @property
    def certificate(self):
        """Returns a TLSCertificate object.

        @return:
        @rtype: TLSCertificate
        """
        return TLSCertificate(self.data.get('certificate', {}))


class TLSCertificate:
    def __init__(self, data):
        """Model for a instance it's TLS certificate-data.

        @param data: dict with the instance it's TLS certificate-data.
        @type data: dict
        """
        self._data = data

    @property
    def data(self): return self._data

    @property
    def version(self):
        """
        @return:
        @rtype: int
        """
        return self.data.get('version', 0)

    @property
    def issuer(self):
        """
        @return:
        @rtype: TLSCertificateIssuer
        """
        return TLSCertificateIssuer(self.data.get('issuer', {}))


class TLSCertificateIssuer:
    def __init__(self, data):
        """Model for a instance it's TLS certificate-issuer-data.

        @param data: dict with the instance it's
                    TLS certificate-issuer-data.
        @type data: dict
        """
        self._data = data

    @property
    def data(self): return self._data

    @property
    def commonName(self):
        """
        @rtype: str
        """
        return self.data.get('commonName', "")

    @property
    def countryName(self):
        """
        @rtype: str
        """
        return self.data.get('countryName', "")

    @property
    def organizationName(self):
        """
        @rtype: str
        """
        return self.data.get('organizationName', "")


class Network:
    def __init__(self, data):
        """Model for a instance it's network data.

        @param data: dict with the instance it's network data.
        @type data: dict
        """
        self._data = data

    @property
    def data(self): return self._data

    @property
    def ipv6(self):
        """
        @return: If ipv6 is enabled on this instance.
        @rtype: bool
        """
        return self.data.get('ipv6', False)

    @property
    def ips(self):
        """
        @return: A list with NetworkIP objects
        @rtype: list
        """
        return [NetworkIP(ip, data)
                for ip, data in self.data.get('ips', {}).items()]

    @property
    def asnPrivacy(self):
        """
        @return: -1 no asn privacy, 0 asn privacy, -2 unknown.
        @rtype: int
        """
        return self.data.get('asn_privacy', -2)


class NetworkIP:
    def __init__(self, ip, data):
        """Model for a network ip data.

        @param ip: ip address
        @type ip: str

        @param data: dict with a network ip data.
        @type data: dict
        """
        self._ip = ip
        self._data = data

    def __repr__(self): return self.ip
    def __str__(self): return repr(self)

    @property
    def ip(self):
        """
        @return: ip address
        @rtype: str
        """
        return self._ip

    @property
    def data(self): return self._data

    @property
    def reverse(self):
        """
        @return:
        @rtype: str
        """
        return self.data.get('reverse', None)

    @property
    def fieldType(self):
        """
        @return: Record type
        @rtype: str
        """
        return self.data.get('field_type', None)

    @property
    def asnCidr(self):
        """
        @return:
        @rtype: str
        """
        return self.data.get('asn_cidr', None)

    @property
    def httpsPort(self):
        """
        @return:
        @rtype: bool
        """
        return self.data.get('https_port', None)


class Instance:
    def __init__(self, url, data):
        """Model for a SearX instance.

        @param url: Url of the instance
        @type url: str

        @param data: Data of the instance
        @type data: dict
        """
        self._url = url
        self._data = data

    def __str__(self): return self.url

    def __repr__(self): return str(self)

    @property
    def data(self): return self._data

    @property
    def url(self):
        """
        @return: returns the url of this instance.
        @rtype: str
        """
        return self._url

    @property
    def main(self):
        """Returns False when not set.

        @return: ?
        @rtype: bool
        """
        return self._data.get('main', False)

    @property
    def networkType(self):
        """Returns a empty string when none found.

        Note: http, https and i2p return 'normal'; tor returns 'tor'.

        @return: Network protocol used (normal or tor)
        @rtype: str
        """
        return self._data.get('network_type', '')

    @property
    def version(self):
        """Returns a empty string when none found.

        @return: Returns the instance it's version.
        @rtype: str
        """
        return self._data.get('version', '')

    @property
    def engines(self):
        """
        Returns a empty string when none found.

        @return: Returns a list with SearchEngine objects
        @rtype: list
        """
        return [SearchEngine(name, data) for name, data in self._data.get(
            'engines', {}).items()]

    @property
    def tls(self):
        """
        @rtype: TLS
        """
        return TLS(self._data.get('tls', {}))

    @property
    def network(self):
        """
        @rtype: TLS
        """
        return Network(self._data.get('network', {}))


class Instances:
    URL = "https://searx.space/"

    def __init__(self):
        self._instances = {}

    def __contains__(self, url):
        return bool(url in self._instances)

    def __iter__(self): return iter(self._instances)

    def __getitem__(self, url): return self._instances[url]

    def __str__(self): return str([url for url in self])

    def __repr__(self): return str(self)

    def __len__(self): return len(self._instances)

    def items(self): return self._instances.items()

    def keys(self): return self._instances.keys()

    def values(self): return self._instances.values()

    def copy(self): return self._instances.copy()

    def data(self):
        """
        @return: dict with as key the instance it's URL and as value
                the instance it's data.
        @rtype data: dict
        """
        r = {}
        for url, instance in self._instances.items():
            r.update({url: instance.data})
        return r

    def setData(self, data):
        """
        @type data: dict
        """
        self.clear()
        for url, instanceData in data.items():
            self._instances.update({url: Instance(url, instanceData)})

    def clear(self):
        """ Clear the instances.
        """
        self._instances.clear()

    def update(self, requestKwargs={}):
        """Fetches list with instances from a searx-stats2 instance.

        @param requestKwargs: Kwargs to pass to request.get
        @type requestKwargs: dict

        @return: A tuple (bool Success/Failed, str Message)
        @rtype: tuple
        """

        url = Instances.URL.rstrip('/') + '/data/instances.json'

        try:
            response = requests.get(url, **requestKwargs)
        except requests.exceptions.HTTPError as err:
            return (False, err)
        except requests.exceptions.ConnectionError as err:
            return (False, err)
        except requests.exceptions.Timeout as err:
            return (False, err)

        if response.status_code == 200:
            try:
                jsonObj = json.loads(response.content)
            except json.JSONDecodeError as err:
                return (False, err)
            else:
                self.setData(jsonObj.get('instances', {}))
                return (True, "Success.")

        return (False, "Wrong response status code: `{0}`."
                .format(response.status_code))
